Skip to content

src: support V8 sandbox memory cage in allocators#62237

Open
codebytere wants to merge 1 commit intonodejs:mainfrom
codebytere:sandbox-tweak
Open

src: support V8 sandbox memory cage in allocators#62237
codebytere wants to merge 1 commit intonodejs:mainfrom
codebytere:sandbox-tweak

Conversation

@codebytere
Copy link
Member

@codebytere codebytere commented Mar 13, 2026

When V8_ENABLE_SANDBOX is enabled, all ArrayBuffer backing stores must be allocated within the V8 memory cage — external pointers cannot be directly wrapped and must be copied into V8-managed memory instead. This commit refactors allocators in node_buffer.cc, node_serdes.cc, and node_trace_events.cc to route allocations through ArrayBuffer::Allocator::NewDefaultAllocator() when the sandbox is enabled, ensuring memory lands inside the cage. In node_serdes.cc, ValueSerializer::Delegate is also extended with ReallocateBufferMemory/FreeBufferMemory overrides so the serializer's internal buffer is cage-allocated from the start.

Tested by making the following change:

diff --git a/configure.py b/configure.py
index fa47e9c4854..f4342d2c718 100755
--- a/configure.py
+++ b/configure.py
@@ -2047,7 +2047,7 @@ def configure_v8(o, configs):
   # Until we manage to get rid of all those, v8_enable_sandbox cannot be used.
   # Note that enabling pointer compression without enabling sandbox is unsupported by V8,
   # so this can be broken at any time.
-  o['variables']['v8_enable_sandbox'] = 0
+  o['variables']['v8_enable_sandbox'] = 1 if options.enable_pointer_compression else 0
   # We set v8_enable_pointer_compression_shared_cage to 0 always, even when
   # pointer compression is enabled so that we don't accidentally enable shared
   # cage mode when pointer compression is on.

and running with ./configure --ninja --experimental-enable-pointer-compression

This allows Electron to reduce/remove a patch.

@codebytere codebytere requested a review from jasnell March 13, 2026 10:45
@nodejs-github-bot nodejs-github-bot added c++ Issues and PRs that require attention from people who are familiar with C++. lib / src Issues and PRs related to general changes in the lib or src directory. needs-ci PRs that need a full CI run. labels Mar 13, 2026
@codecov
Copy link

codecov bot commented Mar 13, 2026

Codecov Report

❌ Patch coverage is 85.71429% with 6 lines in your changes missing coverage. Please review.
✅ Project coverage is 89.66%. Comparing base (da5843b) to head (78e9555).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
src/node_serdes.cc 84.61% 0 Missing and 6 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #62237      +/-   ##
==========================================
- Coverage   89.67%   89.66%   -0.02%     
==========================================
  Files         676      676              
  Lines      206469   206503      +34     
  Branches    39537    39543       +6     
==========================================
+ Hits       185157   185161       +4     
- Misses      13448    13457       +9     
- Partials     7864     7885      +21     
Files with missing lines Coverage Δ
src/api/environment.cc 78.92% <ø> (ø)
src/node_buffer.cc 67.91% <ø> (-0.10%) ⬇️
src/node_trace_events.cc 82.07% <100.00%> (ø)
src/node_serdes.cc 70.23% <84.61%> (+1.69%) ⬆️

... and 27 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

When V8_ENABLE_SANDBOX is enabled, all ArrayBuffer backing
stores must be allocated within the V8 memory cage — external
pointers cannot be directly wrapped and must be copied into
V8-managed memory instead. This commit refactors allocators in
node_buffer.cc, node_serdes.cc, and node_trace_events.cc to
route allocations through ArrayBuffer::Allocator::NewDefaultAllocator()
when the sandbox is enabled, ensuring memory lands inside the cage.
In node_serdes.cc, ValueSerializer::Delegate is also extended with
ReallocateBufferMemory/FreeBufferMemory overrides so the serializer's
internal buffer is cage-allocated from the start.
Copy link
Member

@addaleax addaleax left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reading through the diff ... why doesn't the version of ArrayBuffer::NewBackingStore() that takes a size argument use the default allocator if that's a requirement anyway? Wouldn't it be a bug in V8 if that variant of ArrayBuffer::NewBackingStore() is effectively unusable?

If we indeed need to manually reach out to the default allocator, it would probably be better to create a helper that covers all of these cases and handles the V8_ENABLE_SANDBOX switching in one location instead of those being spread out throughout the codebase

Copy link
Member

@addaleax addaleax left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This definitely fixes issues building Node.js with sandbox mode enabled, but it's still a bit unclear how this interacts with fb21829 and why specifically ArrayBuffer::NewBackingStore(isolate, size) doesn't return a backing store that is valid for the given isolate

size_t size = static_cast<size_t>(value);
#ifdef V8_ENABLE_SANDBOX
CHECK_LE(size, kMaxSafeBufferSizeForSandbox);
CHECK_LE(size, v8::internal::kMaxSafeBufferSizeForSandbox);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
CHECK_LE(size, v8::internal::kMaxSafeBufferSizeForSandbox);
CHECK_LE(size, ArrayBuffer::kMaxByteLength);

We shouldn't access v8::internal unless necessary – ArrayBuffer::kMaxByteLength is defined as v8::internal::kMaxSafeBufferSizeForSandbox in V8_ENABLE_SANDBOX mode

// (which uses the default IsolateGroup's sandbox) are valid for all
// isolates. Creating new groups would give each group its own sandbox,
// causing a mismatch with the allocator.
if (IsolateGroup::CanCreateNewGroups()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this function even return true if V8_ENABLE_SANDBOX is set?

void* Reallocate(void* data, size_t old_length, size_t new_length) {
if (old_length == new_length) return data;
uint8_t* new_data = reinterpret_cast<uint8_t*>(
GetAllocator()->AllocateUninitialized(new_length));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This leaks the v8::ArrayBuffer::Allocator* object

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, nvm, I saw that the allocator is defined as static... that's not a real leak then, although it would of course be nice to have proper memory cleanup in place

@addaleax addaleax dismissed their stale review March 16, 2026 15:27

Memory leak is only real in specific scenarios

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

c++ Issues and PRs that require attention from people who are familiar with C++. lib / src Issues and PRs related to general changes in the lib or src directory. needs-ci PRs that need a full CI run.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants